/*
 * @Description: 
 * @version: 
 * @Author: hui.wang01
 * @Date: 2020-12-17 19:43:13
 * @LastEditors: hui.wang01
 */

//  https://juejin.cn/post/6844904077537574919#heading-55  promise题目来源
// 问题一：使用Promise实现每隔1秒输出1,2,3
// const arr = [1,2,3]; 
// console.log('start',new Date()*1)
// arr.reduce((pre,current)=>{
//   return new Promise((resolve,reject)=>{
//           setTimeout(()=>{
//             console.log(new Date()*1,':',current)
//           },1000)
//           resolve()
//         })
// },0)

//等同于下面的内容
// console.log('start then',new Date()*1)
// arr.reduce((p, x) => {
//   return p.then(() => {
//     return new Promise(r => {
//       setTimeout(() => r(console.log(new Date()*1,':',x)), 1000)
//     })
//   })
// }, Promise.resolve())

//问题一答案改造后，结果发生了变化
// const result = arr.reduce((p, x) => p.then(new Promise(r => setTimeout(() => r(console.log(x)), 1000))), Promise.resolve());

//输出结果：几乎在同一时间输出1，2，3
// 原因：p.then()里面的参数如果不是函数的话，会发生透传
// 但是发生透传，.then()里的代码依然会执行


// finally不管Promise的状态是resolved还是rejected都会执行，且它的回调函数是接收不到Promise的结果的
// .finally的返回值如果在没有抛出错误的情况下默认会是上一个Promise的返回值
// const p1 = new Promise((resolve) => {
//   setTimeout(() => {
//     resolve('resolve3');
//     console.log('timer1')
//   }, 0)
//   resolve('resovle1');
//   resolve('resolve2');
// }).then(res => {
//   console.log(res)
//   setTimeout(() => {
//     console.log(p1)
//   }, 1000)
// }).finally(res => {
//   console.log('finally', res)
// })
// 输出：
// resovle1
// finally undefined
// undefined
// timer1
// Promise {<resolved>: undefined}


// 题目2：使用Promise实现红绿灯交替重复亮
// 红灯3秒亮一次，黄灯2秒亮一次，绿灯1秒亮一次；如何让三个灯不断交替重复亮灯？（用Promise实现）三个亮灯函数已经存在：
// function red() {
//   console.log(new Date()*1,'red');
// }
// function green() {
//   console.log(new Date()*1,'green');
// }
// function yellow() {
//   console.log(new Date()*1,'yellow');
// }

// let lightPromise =function(){ // tips: 将promise封装在一个方法中，方便调用
//   Promise.resolve().then(()=>{  //tips:Promise.resolve()开启一个promise任务
//     //红灯亮
//     return stepLight(red, 3000)
//   }).then(()=>{
//     //绿灯亮
//     return stepLight(green, 2000) // tips: 不要忘记return!!! 
//   }).then(()=>{
//     //黄灯亮
//     return stepLight(yellow, 1000) // tips: 不要忘记return!!!
//   }).then(()=>{
//     //循环
//     lightPromise()
//   })
// }

// let stepLight = function (cb, time) {
//   return new Promise((resolve,reject)=>{
//     setTimeout(()=>{
//       cb()
//       resolve() // tips:这一步不要忘记了 ！！
//     },time)
//   })
// }
 
// lightPromise()

// 题目3： 实现mergePromise函数
// 实现mergePromise函数，把传进去的数组按顺序先后执行，并且把返回的数据先后放到数组data中。
// let promise = Promise.resolve();
// const time = (timer) => {
//   return new Promise(resolve => {
//     setTimeout(() => {
//       resolve()
//       console.log('promise 状态',promise)
//     }, timer)
//   })
// }
// const ajax1 = () => time(2000).then(() => {
//   console.log(1);
//   return 1
// })
// const ajax2 = () => time(1000).then(() => {
//   console.log(2);
//   return 2
// })
// const ajax3 = () => time(1000).then(() => {
//   console.log(3);
//   return 3
// })
// function mergePromise (ajaxArray) {
//   //保存结果，返回promise
//   let promise = Promise.resolve()
//   let result = []
// ajaxArray.forEach(ajaxFn => {
//
//     promise = promise.then(ajaxFn).then(res => {
//       result.push(res)
//       return result //???Promise会将返回的数据类型包装成promise??
//     })
//     
//   });
//  
//   return promise 
// }

// 解题思路：
// 定义一个数组data用于保存所有异步操作的结果
// 初始化一个const promise = Promise.resolve()，然后循环遍历数组，在promise后面添加执行ajax任务，同时要将添加的结果重新赋值到promise上。


// 参考答案

// function mergePromise (ajaxArray) {
//   // 在这里写代码
//   // 存放每个ajax的结果
//   const data = [];
//   // let promise = Promise.resolve();
//   ajaxArray.forEach(ajax => {
//     // 第一次的then为了用来调用ajax   tips:then的参数是函数，非函数会发生值透传
//     // 第二次的then是为了获取ajax的结果
//     promise = promise.then(ajax).then(res => {
//       data.push(res)
//       return data // 把每次的结果返回, 返回的结果在下一次.then()中可以取到;
//     })
//   })
//   // 最后得到的promise它的值就是data
//   return promise;
// }
// mergePromise([ajax1, ajax2, ajax3]).then(data => {
//   console.log("done");
//   console.log(data); // data 为 [1, 2, 3]
// });

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]


// 题目4: 封装一个异步加载图片的方法

// 这个相对简单一些，只需要在图片的onload函数中，使用resolve返回一下就可以了。

// function asyncLoadPic (url) {
//   return new Promise((resolve,reject) => {
//     let img = new Image()
//     img.onload = function () {
//       console.log('图片加载完成')
//       resolve()
//     }
//     img.onerror = function () {
//       console.log('图片加载异常')
//       reject()
//     }
//     img.src = url    
//   }) 
// } 

// 题目5： 限制异步操作的并发个数并尽可能快的完成全部
// 有8个图片资源的url，已经存储在数组urls中。
// urls类似于['https://image1.png', 'https://image2.png', ....]
// 而且已经有一个函数function loadImg，输入一个url链接，返回一个Promise，该Promise在图片下载完成的时候resolve，下载失败则reject。
// 但有一个要求，任何时刻【同时下载的链接数量不可以超过3个】。

// 请写一段代码实现这个需求，要求【尽可能快速】地将所有图片下载完成。
var urls = [
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
  "https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
  //   // 在这里写代码
  // console.log('url:',new Date()*1,url)
  return new Promise((resolve,reject) => {
    let img = new Image()
    img.onload = function () {
      console.log('图片加载完成',new Date()*1,url)
      resolve()
    }
    img.onerror = function () {
      console.log('图片加载异常',url)
      reject()
    }
    img.src = url    
  })
}


/*
第一步(初始化一些内容)：先复制传进来的urls,sequence的长度为8, 
      然后取sequence数组的前面3个组成一组Promise请求promises,
      取数组的前三个要用splice切割，这样sequence的长度就变成5
第二步(循环调用)： 接着用reduce遍历数组sequence，reduce的初始值为Promise.resolve(),
      第一步中的promises用Promise.race来加载，例如Promise.race(promises)，
      一次循环得到的是promises中已经加载完的那个下标fastestIndex
第三步(并发处理)： 在Promise.race()的.then()函数中获取到fasetestIndex,
      再将promise[fastestIndex]这一项替换成还没有加载的请求，
      过程中可以使用catch来捕获异常
第四步(返回值)： sequence已经全部遍历完了，得到一组还在加载的promises,
    用Promise.all()来加载promises,
    全部执行完，得到的应该也是一个Promise可以接着用链式调用
*/

function limitLoad(urls, handler, limit) {
  let sequence = [].concat(urls); // 复制urls
  // 这一步是为了初始化 promises 这个"容器"
  let promises = sequence.splice(0, limit).map((url, index) => {
    return handler(url).then(() => {
      // 返回下标是为了知道数组中是哪一项最先完成
      console.log('promises index:',index)
      return index;
    });
  });
  console.log('promises length',promises.length)
  debugger
  // 注意这里要将整个变量过程返回，这样得到的就是一个Promise，可以在外面链式调用
  return sequence
    .reduce((pCollect, url) => {
      return pCollect
        .then(() => {
          return Promise.race(promises); // 返回已经完成的下标
        })
        .then(fastestIndex => { // 获取到已经完成的下标
          // 将"容器"内已经完成的那一项替换
          console.log('fastestIndex', fastestIndex)
          promises[fastestIndex] = handler(url).then(
            () => {
              return fastestIndex; // 要继续将这个下标返回，以便下一次变量
            }
          );
        })
        .catch(err => {
          console.error(err);
        });
    }, Promise.resolve()) // 初始化传入
    .then(() => { // 最后三个用.all来调用
      console.log('promises',promises)
      return Promise.all(promises);
    })
}
limitLoad(urls, loadImg, 3)
  .then(res => {
    console.log("图片全部加载完毕");
    console.log(res);
  })
  .catch(err => {
    console.error(err);
  });



 











